package org.pool.custom;

import org.common.constant.GlobalConfig;
import org.common.util.JDBCUtil;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.LinkedList;
import java.util.Properties;

/**
 * 传统获取连接方式的缺陷:
 * Connection对象在每次执行DML和DQL的过程中都要创建一次,DML和DQL执行完毕后,connection对象都会被销毁。而该对象的创建和销毁都是比较消耗系统资源的。
 * 但是,connection对象是应该可以反复使用的,没有必要每次都创建新的。如何实现connection对象的反复使用呢?使用连接池技术实现。
 * <p>
 * 连接池方式:
 * 连接池解决方案是在应用程序启动时就预先建立多个数据库连接对象,然后将连接对象保存到连接池中。
 * 当客户请求到来时,从池中取出一个连接对象为客户服务。当请求完成时,程序只需要调用将连接对象放回池中的方法即可而非直接调用close()来销毁。
 * 而对于超过了连接池中连接数的请求则另外创建连接进行处理(因为不可能说让其他用户就这么等着连接回归)。
 * 由此,我们的应用程序就还可以根据连接池中连接的使用率,动态增加或减少池中的连接数。
 * <p>
 * 连接池的优势:
 * 1、预先准备了连接对象,这样当多个线程并发执行时,可以避免短时间内一次性大量创建连接对象。减少计算机单位时间内的运算压力,提高程序的响应速度。
 * 2、实现了连接对象的反复使用,可以大大减少连接对象的创建次数,减少资源的消耗
 * <p>
 * TIPS:连接池本身已经有很多成熟的技术了: 比如Druid、DBCP、C3P0、HikariCP等。
 * 但是鉴于大部分人都只是简单的引入就完事,对连接池具体的原理都不大了解的缘故,这里就不引入它们,而采用自己实现的一个非常简单的连接池来解说。
 *
 * @author yexi
 */
public class CustomConnectionPool {

    /**
     * 连接池初始化连接数
     */
    private static final int INIT_SIZE = 5;

    /**
     * 连接池最大可承载连接数。避免过多的连接数造成内存容量的压力(有时大量的连接是并发时产生的而非平时的状态,所以需要该参数的存在)。
     */
    private static final int MAX_SIZE = 10;

    private static final Properties PROPERTIES = JDBCUtil.getProperties();

    private static final LinkedList<Connection> POOL;

    static {
        // 初始化连接池
        POOL = new LinkedList<>();
        for (int i = 0; i < INIT_SIZE; i++) {
            Connection connection = initConnection();
            if (null != connection) {
                POOL.add(connection);
                System.out.println("初始化连接" + connection.hashCode() + "放入连接池");
            }
        }
    }

    /**
     * 初始化连接对象的方法
     */
    private static Connection initConnection() {
        try {
            String active = PROPERTIES.getProperty("active");
            return DriverManager.getConnection(
                    PROPERTIES.getProperty(GlobalConfig.getUrl(active)),
                    PROPERTIES.getProperty(GlobalConfig.getUsername(active)),
                    PROPERTIES.getProperty(GlobalConfig.getPassword(active))
            );
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 向外界提供连接对象的方法
     */
    public static Connection getConnection() {
        Connection connection = null;
        int size = POOL.size();
        if (size > 0) {
            // 移除集合中的第一个元素
            connection = POOL.removeFirst();
            System.out.println("从连接池取出连接：" + connection.hashCode());
            System.out.println("目前连接池中剩余连接数：" + size);
        } else {
            connection = initConnection();
            System.out.println("目前连接池中连接已用尽,创建新连接：" + connection.hashCode());
        }
        return connection;
    }

    /**
     * 向外界提供的归还连接对象的方法
     */
    public static void returnConnection(Connection connection) {
        if (null != connection) {
            try {
                if (!connection.isClosed()) {
                    if (POOL.size() < MAX_SIZE) {
                        try {
                            // 调整事务状态,避免有些连接使用过程中对事务的自动提交做了修改
                            connection.setAutoCommit(true);
                            System.out.println("设置连接：" + connection.hashCode() + "自动提交为true");
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                        POOL.addLast(connection);
                        System.out.println("连接：" + connection.hashCode() + "已归还");
                    } else {
                        try {
                            connection.close();
                            System.out.println("连接池已满,该连接：" + connection.hashCode() + "直接关闭即可");
                        } catch (SQLException e) {
                            e.printStackTrace();
                        }
                    }
                } else {
                    System.out.println("连接：" + connection.hashCode() + "已经关闭,无需归还");
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("传入的连接为null,不可归还");
        }
    }
}
